<?php

namespace Shahu\Common\service;

use DateMalformedStringException;
use DateTimeImmutable;
use Exception;
use Lcobucci\JWT\Encoding\ChainedFormatter;
use Lcobucci\JWT\Encoding\JoseEncoder;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Token\Builder;
use Lcobucci\JWT\Token\Parser;
use Lcobucci\JWT\Validation\Constraint\IssuedBy;
use Lcobucci\JWT\Validation\Constraint\PermittedFor;
use Lcobucci\JWT\Validation\Constraint\RelatedTo;
use Lcobucci\JWT\Validation\Validator;
use Random\RandomException;
use Shahu\Common\common\RedisCache;
use Shahu\Common\constant\AuthRedisKeyConstant;
use Shahu\Common\exception\AuthException;
use Shahu\Common\model\AdminModel;

class AuthService
{
    /**
     * 获取admin token
     * @param $user_id
     * @return array
     * @throws RandomException
     */
    public static function getAdminToken($user_id): array
    {
        //获取token
        $token_info = static::createToken($user_id);

        //设置缓存
        RedisCache::set(
            AuthRedisKeyConstant::ADMIN_TOKEN.$user_id,
            $token_info['access_token'],
            $token_info['expires_in_min']
        );

        //返回
        return $token_info;
    }


    /**
     * 验证token
     * @param string $access_token
     * @return array
     * @throws AuthException
     */
    public static function validateToken(string $access_token): array
    {
        $user_id = static::parseToken($access_token);
        //验证用户ID
        $user = AdminModel::selectOneById($user_id);
        if (empty($user)) {
            throw new AuthException('用户不存在');
        }

        //ip校验
        if (($GLOBALS['ip'] ?? 0) != $user['ip']) {
            throw new AuthException('登录ip有变化，请重新登录');
        }

        //验证是否当前缓存token
        $cache_token = RedisCache::get(AuthRedisKeyConstant::ADMIN_TOKEN.$user_id);
        if ($access_token != $cache_token) {
            throw new AuthException('用户令牌验证已失效');
        }

        //返回用户信息
        return $user;
    }



    /**
     * 创建token
     * @param $user_id
     * @return array
     * @throws RandomException|DateMalformedStringException
     */
    private static function createToken($user_id): array
    {
        //生成token
        $tokenBuilder = (new Builder(new JoseEncoder(), ChainedFormatter::default()));
        $algorithm = new Sha256();
        $signingKey = InMemory::plainText(random_bytes(32));

        $now = new DateTimeImmutable();
        $token = $tokenBuilder
            // 配置发行人
            ->issuedBy(getenv('JWT_ISSUED'))
            // 配置受众
            ->permittedFor(getenv('JWT_FOR'))
            // 配置令牌的主题
            ->relatedTo(getenv("JWT_RELATED"))
            // 配置令牌的颁发时间
            ->issuedAt($now)
            // 配置令牌可以使用的时间
            ->canOnlyBeUsedAfter($now->modify('+'.getenv('JWT_TTL_DAY').' day'))
            // 配置令牌的过期时间
            ->expiresAt($now->modify('+'.getenv('JWT_TTL_DAY').' day'))
            // 用户ID
            ->withClaim('admin_user_id', $user_id)
            // 租户header头
            ->withHeader('tenant_id', $GLOBALS['tenant_id'])
            // Builds a new token
            ->getToken($algorithm, $signingKey);

        return [
            'access_token' => $token->toString(),
            'token_type' => 'bearer',
            'expires_in_min' => getenv('JWT_TTL_DAY') * 24 * 60,
            'expires_in' => getenv('JWT_TTL_DAY') * 24 * 60 * 60
        ];
    }


    /**
     * 解析token
     * @param String $access_token
     * @return mixed
     * @throws AuthException
     */
    public static function parseToken(string $access_token) : mixed
    {
        //解析
        $parser = new Parser(new JoseEncoder());
        try {
            $token = $parser->parse($access_token);

            //验证
            $validator = new Validator();
            if (!$validator->validate($token, new IssuedBy(getenv('JWT_ISSUED')))) {
                throw new AuthException();
            }
            if (!$validator->validate($token, new PermittedFor(getenv('JWT_FOR')))) {
                throw new AuthException();
            }
            if (!$validator->validate($token, new RelatedTo(getenv('JWT_RELATED')))) {
                throw new AuthException();
            }
        } catch (Exception $e) {
            throw new AuthException('用户令牌解析异常');
        }

        //检查tenant是否相等
        $token_tenant_id = $token->headers()->get('tenant_id');
        if (empty($token_tenant_id || ($GLOBALS['tenant_id'] ?? '') != $token_tenant_id)) {
            throw new AuthException('token验证不通过');
        }

        //时间是否已经过期
        $now = new DateTimeImmutable();
        if ($token->isExpired($now)) {
            throw new AuthException('用户令牌已过期');
        }

        //检查用户
        $user_id = $token->claims()->get('admin_user_id');
        if (empty($user_id)) {
            throw new AuthException('用户令牌信息异常');
        }
        //返回用户ID
        return $user_id;
    }


    public static function deleteToken($user_id): void
    {
        RedisCache::del(AuthRedisKeyConstant::ADMIN_TOKEN.$user_id);
    }
}